package cn.omisheep.au.core;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @author zhou xin chen  😊
 * 联系方式 qq:1269670415  email:xinchenzhou666@gmail.com
 */

@Slf4j
public class AuValidate {

    private final AuConfig auConfig;
    private final Map<RequestMethod, Map<String, Set<String>>> auMap;
    private final Map<RequestMethod, Map<String, Set<String>>> auExcludeMap;
    private final Map<RequestMethod, Map<String, AuCore.LimitMeta>> auLimitMap;
    /**
     * 所有ip请求时如果不是第一次请求，则判断是否在该接口黑名单，如果在，判断是否到时间了，到了则删除，否则返回error
     * 第一次请求时将ip保存，同时保存当前时间，次数设为1，再次请求时，如果map里有这个ip则不新建，而是将次数+1，
     * 当次数达到上限该接口的上限，判断 超过时的时间-第一次请求的时间是否 > 你所规定的时间，如果大于，则将该请求覆盖之前的请求，否则加入黑名单，同时加入时间
     */
    private final HashMap<RequestMethod, MethodPool> map = new HashMap<>();

    public static class MethodPool extends HashMap<String, HashMap<String, IpMeta>> {
//        public HashMap<String, HashMap<String, IpMeta>> $ = new HashMap<>();
    }

    @Data
    @AllArgsConstructor
    private static class IpMeta {
        public long requestTime;
        public int count;
        public boolean ban;
    }

    private final AntPathMatcher antPathMatcher = new AntPathMatcher("/");

    public AuValidate(AuConfig auConfig, AuCore auCore) {
        this.auConfig = auConfig;
        this.auLimitMap = auCore.getAuLimitMap();
        this.auMap = auCore.getAuMap();
        this.auExcludeMap = auCore.getAuExcludeMap();
        for (RequestMethod method : RequestMethod.values()) {
            MethodPool methodPool = new MethodPool();
            for (String path : auLimitMap.get(method).keySet()) {
                methodPool.put(path, new HashMap<>());
            }
            this.map.put(method, methodPool);
        }
    }

    public RequestStatus validate(HttpServletRequest request) {
        return validate(request, auConfig.getAnyPermDefault());
    }

    public RequestStatus validate(HttpServletRequest request, String role) {
        return validate(request, Arrays.asList(role.split(auConfig.getPermSeparator())));
    }

    private RequestStatus validate(HttpServletRequest request, List<String> roles) {
        RequestMethod method = RequestMethod.valueOf(request.getMethod()); // method
        String uri = request.getRequestURI(); // uri
        String ip = request.getHeader("x-forwarded-for"); // ip
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        for (Map.Entry<String, AuCore.LimitMeta> entry : auLimitMap.get(method).entrySet()) {
            if (antPathMatcher.match(entry.getKey(), uri)) {
                AuCore.LimitMeta limitMeta = entry.getValue();
                if (limitMeta == null) break;

                HashMap<String, IpMeta> ipPool = map.get(method).get(entry.getKey());
                IpMeta ipMeta = ipPool.get(ip);
                if (ipMeta == null) {
                    log.info("第一次请求  ip : {}  api: {} ", ip, uri);
                    ipPool.put(ip, new IpMeta(new Date().getTime(), 1, false));
                } else {
                    ipMeta.count++;

                    if (ipMeta.isBan()) {
                        long now = new Date().getTime();
                        long t = now - ipMeta.requestTime;
                        // 如果被ban掉了，那么看是否到时间了，如果没到时间，那么拒绝，否则解除，然后赋予新的访问权限
                        if (t > limitMeta.getRelieveTime()) {
                            log.info("解除封禁  ip : {}  api: {} ", ip, uri);
                            ipMeta = new IpMeta(now, 1, false);
                            ipPool.put(ip, ipMeta);
                        } else {
                            log.info("请求频繁(拒绝)  ip : {}  api: {} ", ip, uri);
                            return RequestStatus.REQUEST_REPEAT;
                        }
                    }

                    if (ipMeta.count > limitMeta.getLimitMaxCount()) {
                        long now = new Date().getTime();
                        long t = now - ipMeta.requestTime;
                        // 如果大于所规定的请求次数，则判断是否时间在你所限制的时间内，如果是，则拉入黑名单，否则覆盖第一次
                        if (t > limitMeta.getLimit()) {
                            log.info("请求刷新  ip : {}  api: {} ", ip, uri);
                            ipPool.put(ip, new IpMeta(now, 1, false));
                        } else {
                            log.info("请求频繁(封禁)  ip : {}  api: {} ", ip, uri);
                            ipPool.get(ip).setBan(true);
                            return RequestStatus.REQUEST_REPEAT;
                        }
                    }

                }
                break;
            }
        }

        String forbidPermDefault = auConfig.getForbidPermDefault();
        String anyPermDefault = auConfig.getAnyPermDefault();
        Set<String> requireRoles = new HashSet<>(Collections.singletonList(anyPermDefault));
        Set<String> forbidRoles = new HashSet<>(Collections.singletonList(forbidPermDefault));

        for (Map.Entry<String, Set<String>> entry : auMap.get(method).entrySet()) {
            if (antPathMatcher.match(entry.getKey(), uri)) {
                requireRoles = entry.getValue();
                forbidRoles = auExcludeMap.get(method).get(entry.getKey());
                break;
            }
        }

        // 如果路径上有any权限，则默认所有人都可以访问
        if (requireRoles.contains(anyPermDefault)) {
            logs("success", uri, method, ip, roles, requireRoles, forbidRoles);
            return RequestStatus.SUCCESS;
        }
        // 如果路径上有any权限，则默认所有人都不可以访问
        if (forbidRoles.contains(anyPermDefault)) {
            logs("forbid", uri, method, ip, roles, requireRoles, forbidRoles);
            return RequestStatus.PERM_EXCEPTION;
        }

        if (roles == null) {
            roles = Collections.singletonList(anyPermDefault);
        }

        if (hasRole(forbidRoles, roles)) {
            logs("forbid", uri, method, ip, roles, requireRoles, forbidRoles);
            return RequestStatus.PERM_EXCEPTION;
        }

        if (!hasRole(requireRoles, roles)) {
            logs("forbid", uri, method, ip, roles, requireRoles, forbidRoles);
            return RequestStatus.PERM_EXCEPTION;
        }

        logs("success", uri, method, ip, roles, requireRoles, forbidRoles);
        return RequestStatus.SUCCESS;

    }

    private boolean hasRole(Set<String> sourceRoles, List<String> targetRoles) {
        if (sourceRoles == null) return false;
        if (targetRoles == null) return false;
        return sourceRoles.stream().anyMatch(targetRoles::contains);
    }

    private void logs(String status, String uri, RequestMethod method, String ip, List<String> roles, Set<String> requireRoles, Set<String> forbidRoles) {
        log.info("!! {} !!  Request uri: [{}], method: [{}], ip: [{}], role: {} requireRoles: {} forbidRoles: {}",
                status, uri, method, ip, roles, requireRoles, forbidRoles);
    }

    public Map<RequestMethod, Map<String, Set<String>>> getAuMap() {
        return auMap;
    }

    public Map<RequestMethod, Map<String, Set<String>>> getAuExcludeMap() {
        return auExcludeMap;
    }

    public Map<RequestMethod, Map<String, AuCore.LimitMeta>> getAuLimitMap() {
        return auLimitMap;
    }

}
